package com.sc.demo.plugins;

import java.lang.annotation.Annotation;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Date;
import java.util.Map;
import java.util.Properties;

import org.apache.ibatis.executor.statement.BaseStatementHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.log4j.Logger;
import org.springframework.util.Assert;

import com.sc.demo.annotation.Model;
import com.sc.demo.annotation.TableRouter;
import com.sc.demo.router.Router;
import com.sc.demo.router.Router.RouterArithmetic;
import com.sc.demo.router.SqlRewriter;
import com.sc.demo.router.arithmetic.DateRouter;
import com.sc.demo.router.arithmetic.DirectRouter;
import com.sc.demo.router.arithmetic.HashRouter;
import com.sc.demo.router.arithmetic.RangeRouter;
import com.sc.demo.router.arithmetic.RedisIdDateRouter;
import com.sc.demo.util.ReflectUtils;


@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class , Integer.class}) })
public class TableRouterPlugin implements Interceptor {
	
	private static Logger logger = Logger.getLogger(TableRouterPlugin.class);
	
	public Object intercept(Invocation invocation) throws Throwable {
		Connection connection = (Connection) invocation.getArgs()[0];
		logger.debug("[TableRouterPlugin] Target =" + (RoutingStatementHandler) invocation.getTarget());
		logger.debug("[TableRouterPlugin] Catalog = " + connection.getCatalog());
		logger.debug("[TableRouterPlugin] Method=" + invocation.getMethod());
		RoutingStatementHandler statement = (RoutingStatementHandler) invocation.getTarget();
		BaseStatementHandler handler = (BaseStatementHandler) ReflectUtils.getFieldValue(statement, "delegate");
		// TableRouter 获得重写规则
		MappedStatement mapper=(MappedStatement)ReflectUtils.getFieldValue(handler, "mappedStatement");
		String mapperName=mapper.getId().substring(0, mapper.getId().lastIndexOf("."));
		Class<?> clazz=Class.forName(mapperName);
		TableRouter rule = (TableRouter) clazz.getAnnotation(TableRouter.class);
		Object params = handler.getBoundSql().getParameterObject();
		if(rule != null && params != null){			
			BoundSql boundSql = handler.getBoundSql();
			String sql = (String) ReflectUtils.getFieldValue(boundSql, "sql");
			logger.debug("[TableRouterPlugin] initSQL=" + sql);
			logger.debug("[TableRouterPlugin] parameterObject =" + params);
			logger.debug("[TableRouterPlugin] parameterMappings =" + handler.getBoundSql().getParameterMappings());
			logger.debug("[TableRouterPlugin] TableRouterAnnotation=" + rule);	
			boolean isCamelCase = mapper.getConfiguration().isMapUnderscoreToCamelCase();			
			Object paramVal = obtainParam(params, rule.column(),isCamelCase);
			logger.debug("[TableRouterPlugin] paramVal=" + paramVal);	
			Assert.notNull(paramVal, "Obtain param["+rule.column()+"] is null!");
			Router router = null;
			if(rule.arithmetic() == RouterArithmetic.hash){
				router = new HashRouter(rule.column(),rule.pattern(),rule.count());
			}else if(rule.arithmetic() == RouterArithmetic.date){
				router = new DateRouter(rule.column(),rule.pattern());
			}else if(rule.arithmetic() == RouterArithmetic.range){
				router = new RangeRouter(rule.column(),rule.pattern());
			}else if(rule.arithmetic() == RouterArithmetic.direct){
				router = new DirectRouter(rule.column(),rule.pattern());
			}else if(rule.arithmetic() == RouterArithmetic.redis_id_date){
                router = new RedisIdDateRouter(rule.column(),rule.pattern());
            }

//			else if(rule.arithmetic() == RouterArithmetic.round){
//				router = new RoundRouter(rule.pattern(),rule.count());
//			}
//			else if(rule.arithmetic() == RouterArithmetic.string_hash){
//				router = new StringHashCodeRouter(rule.column(),rule.pattern(),rule.count());
//			}			
			String finalval = router.doRoute(paramVal);
			logger.debug("[TableRouterPlugin] finalval=" + finalval);	
			String newSQL = SqlRewriter.rewriteSqlTable(sql, rule.table(), finalval);					
			logger.debug("[TableRouterPlugin] overSQL=" + newSQL);
			ReflectUtils.setFieldValue(boundSql, "sql",newSQL);
			ReflectUtils.setFieldValue(handler,"boundSql",boundSql);
		}
		try {
			return prepare(handler, connection);
		} catch (SQLException e) {
			return null;
		}
	}

	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}
	
	@SuppressWarnings("unchecked")
	private Object obtainParam(Object params, String column,boolean isCamelCase){
		Object paramVal = null;
		if(params instanceof String || params instanceof Integer || params instanceof Byte || params instanceof Date 
				|| params instanceof Long){
			paramVal = params;
		}else if(params instanceof Map){
			paramVal = ((Map<String,Object>)params).get(camelCaseConvert(column));
		}else{
			boolean isModel = false;
			Annotation[] annotations = params.getClass().getDeclaredAnnotations();
			if(annotations!=null && annotations.length >0){
				for(Annotation annotation : annotations){
					if(annotation instanceof Model){
						isModel = true;
						break;
					}
				}
			}
			if(isModel){
				paramVal = ReflectUtils.getFieldValue(params, camelCaseConvert(column));
			}
		}
		return paramVal;
	}
	
	public static String camelCaseConvert(String column){
		logger.debug("===============列-属性映射处理======================");
		String[] arr = column.split("_");
		if(arr.length == 1){ 
			return column;
		} else if(arr.length > 1) {
			StringBuilder sb = new StringBuilder();
			for(String s:arr){
				sb.append(s.substring(0,1).toUpperCase()).append(s.substring(1));
			}
			String prop = sb.toString();
			return prop.substring(0, 1).toLowerCase() + prop.substring(1);
		}else{
			return null;
		}
	}

	private Statement prepare(BaseStatementHandler handler, Connection connection) throws SQLException {
		return handler.prepare(connection, null);
	}

	public void setProperties(Properties properties) {
	}
	
}
